#include <string>
#include <mutex>
#include <memory>
#include <functional>
#include <iostream>
#include <fstream>
#include <filesystem>

#include <unistd.h>
#include <sys/types.h>
#include <time.h>
#include <sstream>


using namespace std;

namespace LogMudule
{
    enum class LogLevel // 日志等级
    {
        DEBUG = 1, // 调试
        NORMAL,    // 正常
        WARNING,   // 警告
        ERROR,     // 错误
        DEADLY     // 致命
    };

    string LogLevelToString(const LogLevel& level)//把日志等级转换为字符串
    {
        string s;
        switch(level)
        {
            case LogLevel::DEBUG:
            s="debug";
            break;
            case LogLevel::NORMAL:
            s="normal";
            break;
            case LogLevel::WARNING:
            s="warning";
            break;
            case LogLevel::ERROR:
            s="error";
            break;
            case LogLevel::DEADLY:
            s="deadly";
            break;

            default:
            s="unknown";
            break;
        }
        return s;
    }

    string CurrentTime() // 获取当前时间
    {
        time_t time_stamp = time(nullptr); // 获取当前时间戳
        struct tm curr;

        localtime_r(&time_stamp, &curr); // 把时间戳转换成struct tm对象
        char buf[1024];

        snprintf(buf, sizeof(buf), "%4d-%02d-%02d %02d:%02d:%02d", // 年-月-日 时：分：秒
                 curr.tm_year + 1900,
                 curr.tm_mon + 1,
                 curr.tm_mday,
                 curr.tm_hour,
                 curr.tm_min,
                 curr.tm_sec);
        return buf;
    }

    string default_logfilepath = "./log/";
    string default_logfilename = "log.txt";


    class LogStrategy // 日志策略父类
    {
    public:
        virtual void RenovateLog(const string &Information) = 0; // 使用对应策略刷新日志
        virtual ~LogStrategy()
        {
        }
    };

    class ConsoleLogStrategy : public LogStrategy // 日志策略子类-向控制台打印
    {
    public:
        virtual void RenovateLog(const string &Information) // 使用对应策略刷新日志
        {
            lock_guard<mutex> m(_mutex);
            cout << Information << endl; // 向控制台打印
        }

    private:
        mutex _mutex;
    };

    class FileLogStrategy : public LogStrategy // 日志策略子类-向日志文件打印
    {
        static string default_filepath;
        static string default_filename;

    public:
        FileLogStrategy(const string &path = default_filepath, const string &name = default_filename)
            : _file_path(path),
              _file_name(name)
        {
            lock_guard<mutex> m(_mutex);//加锁，防止目录重复创建，导致错误
            if (filesystem::exists(_file_path)) // 判断日志目录是否存在
            {
                return;
            }
            try 
            {
                filesystem::create_directories(_file_path);// 日志目录不存在，就创建
            }
            catch(const filesystem::filesystem_error &e)
            {
                cerr<<e.what()<<endl;//输出错误内容
            }
        }
        virtual void RenovateLog(const string &Information) // 使用对应策略刷新日志
        {
            lock_guard<mutex> m(_mutex);

            string file = _file_path + _file_name;

            ofstream out(file, ios::app | ios::out); // 以追加方式打开指定日志文件
            if (!out.is_open())
            {
                cerr << "打开日志文件失败" << endl;
                return;
            }
            out << Information << endl; // 向指定的日志类打印
            out.close();
        }

    private:
        mutex _mutex;
        string _file_path; // 日志文件路径
        string _file_name; // 日志文件名
    };
    string FileLogStrategy::default_filepath = "./log/";
    string FileLogStrategy::default_filename = "log.txt";


    class Logger
    {
        class LogInformation // 日志信息类
        {
        public:
            LogInformation(const LogLevel level, const string &file_name, int line_num, function<void(const string &)> renovate)
                : _time(CurrentTime()),
                  _log_level(level),
                  _pid(getpid()),
                  _tid(gettid()),
                  _file_name(file_name),
                  _line_num(line_num),
                  _renovate(renovate)
            {
                stringstream ss;
                ss<<"["<<_time<<"] "<<"["<<LogLevelToString(_log_level)<<"] "
                <<"["<<_pid<<"] "<<"["<<_tid<<"] "<<"["<<_file_name<<"] "<<"["<<_line_num<<"] - ";

                _log_information=ss.str();
            }
            template <typename T>
            LogInformation& operator<<(const T &msg)
            {
                stringstream ss;
                ss<<msg;
                _log_information+=ss.str();
                return *this;
            }
            ~LogInformation()
            {
                _renovate(_log_information);//根据对应策略把日志信息刷新出去
            }
        private:
            string _time;        // 这条日志信息产生时的时间
            LogLevel _log_level; // 这条日志信息对应的日志等级
            pid_t _pid;          // 打印这条日志信息进程的pid
            pid_t _tid;          // 打印这条日志信息线程的tid
            string _file_name;   // 打印这条日志信息时所处的源文件名
            int _line_num;       // 打印这条日志信息时所处的源文件名的行号

            string _log_information;//一条完整的日志信息

            function<void(const string &)> _renovate; // 用于回调Logger(日志类)注册进来的刷新函数
        };
    public:
        Logger()
            : _strategy(make_unique<ConsoleLogStrategy>()) // 默认向控制台打印
        {
        }
        void EnableConsoleStrategy() // 切换刷新策略为控制台策略
        {
            lock_guard<mutex> m(_mutex); // 保护_strategy
            _strategy = make_unique<ConsoleLogStrategy>();
        }
        void EnableFileStrategy(const string &file_path = default_logfilepath,const string &file_name = default_logfilename) // 切换刷新策略为文件策略
        {
            lock_guard<mutex> m(_mutex); // 保护_strategy
            _strategy = make_unique<FileLogStrategy>(file_path,file_name);
        }
        void RenovateInformation(const string &Information) // 刷新日志信息
        {
            lock_guard<mutex> m(_mutex);         // 保护_strategy
            _strategy->RenovateLog(Information); // 使用对应策略对日志信息进行刷新
        }
        LogInformation operator()(LogLevel level,const string& file_name,int line_num)
        {
            return LogInformation(level,file_name,line_num,[this](const string &Information)
            {
                this->RenovateInformation(Information);
            });
        }
    private:
        unique_ptr<LogStrategy> _strategy; // 刷新策略
        mutex _mutex;
    };

Logger Log;//定义日志全局变量
#define LOG(Level) (Log(Level,__FILE__,__LINE__)) //使用日志

void EnableConsoleStrategy()// 切换刷新策略为控制台策略
{
    Log.EnableConsoleStrategy();
} 
void EnableFileStrategy(const string &FilePath =default_logfilepath ,const string &FileName =default_logfilename)// 切换刷新策略为文件策略
{
    Log.EnableFileStrategy(FilePath,FileName);
}
}